/*
 * 实用工具模块
 *
 * 提供日志功能，
 * 定义异常体系，
 * 提供数据库访问功能，
 * 封装文件目录操作，
 * 封装系统信号量，
 *
 **/

#pragma once

#ifdef HTTPS
#define CPPHTTPLIB_OPENSSL_SUPPORT
#endif

#include <easylog.h>
#include <easysql.h>
#include <easyfs.h>
#include <snappy.h>
#include <signal.h>
#if defined (__APPLE__) || defined (__linux__)
#   include <semaphore.h>
#endif
#if defined (_WIN32) || defined (_WIN64)
#   include <Windows.h>
#   include <winbase.h>
#endif
#if defined(__APPLE__) || defined (_WIN32) || defined (_WIN64)
#   include <json/json.h>
#elif defined(__linux__)
#   include <jsoncpp/json/json.h>
#endif

namespace cloud
{

class except : public std::exception
{
public:
    except(const std::string& msg) : s(msg)
    {}

    const char* what() const throw()
    {
        return s.c_str();
    }

    std::string s;
};

namespace util
{

struct file : easyfs::file
{
    file(const std::string& path) : easyfs::file(path) {}

    file& pack(const std::string& packname)
    {
        std::string unpack = read();
        std::string packed;
        snappy::Compress(unpack.data(), unpack.size(), &packed);
        easyfs::write_file(packname, packed);
        return *this;
    }

    file& unpack(const std::string& unpackname)
    {
        std::string packed = read();
        std::string unpack;
        snappy::Uncompress(packed.data(), packed.size(), &unpack);
        easyfs::write_file(unpackname, unpack);
        return *this;
    }

    file& unpack(std::string* content)
    {
        std::string packed = read();
        snappy::Uncompress(packed.data(), packed.size(), content);
        return *this;
    }

    time_t atime() { return easyfs::file::atime(); }
    time_t mtime() { return easyfs::file::mtime(); }
    time_t ctime() { return easyfs::file::ctime(); }
};

struct dir : easyfs::directory
{
    dir(const std::string& path) : easyfs::directory(path) {}

    std::vector<std::string> scan()
    {
        std::vector<easyfs::path> paths = easyfs::directory::scan();
        std::vector<std::string> res;
        for (auto& path : paths)
            res.push_back(std::move(path.string()));
        return res;
    }

    std::vector<std::string> rscan()
    {
        std::vector<easyfs::path> paths = easyfs::directory::rscan();
        std::vector<std::string> res;
        for (auto& path : paths)
            res.push_back(path.string());
        return res;
    }
};

struct json
{
    json() = default;

    json(Json::Value root) : _root(root) {}

    void append(const util::json& js) { _root.append(js._root); }

    size_t size() const { return _root.size(); }

    json operator[](int i) const { return _root[i]; }

    template<typename value_type>
    json& set(const std::string& key, const value_type& value) { throw except("set not implemented for " + key); return *this; }

    template<typename value_type>
    value_type get(const std::string& key) const { throw except("get not implemented for " + key); }

    // template<typename value_type>
    // void set(const std::string& key, const value_type& value)
    // {
    //     _root[key] = value;
    // }
    // template<typename value_type>
    // value_type get(const std::string& key) const
    // {
    //     if (_root.isMember(key) && _root[key].is<value_type>())
    //         return _root[key].as<value_type>();
    //     else
    //         throw except("no " + key);
    // }

    std::string serialize()
    {
        std::unique_ptr<Json::StreamWriter> sw(Json::StreamWriterBuilder().newStreamWriter());
        std::stringstream res;
        if (sw->write(_root, &res) != 0)
            throw except("serialize write error");
        return res.str();
    }

    json& deserialize(const std::string& str)
    {
        std::unique_ptr<Json::CharReader> cr(Json::CharReaderBuilder().newCharReader());

        std::string errs;
        if (!cr->parse(str.c_str(), str.c_str() + str.size(), &_root, &errs))
            throw except("deserialize parse error " + errs);
        return *this;
    }

private:
    void check(const std::string& key) const
    {
        if (!_root.isMember(key))
            throw except("has no value for " + key);
    }

    Json::Value _root;
};

    template<> json& json::set<bool>       (const std::string& key, const bool&        value) { _root[key] = value;           return *this; }
    template<> json& json::set<time_t>     (const std::string& key, const time_t&      value) { _root[key] = (int)value;      return *this; }
    template<> json& json::set<int>        (const std::string& key, const int&         value) { _root[key] = value;           return *this; }
    template<> json& json::set<size_t>     (const std::string& key, const size_t&      value) { _root[key] = (uint64_t)value; return *this; }
    template<> json& json::set<std::string>(const std::string& key, const std::string& value) { _root[key] = value;           return *this; }

    template<>
    bool json::get<bool>(const std::string& key) const
    {
        check(key);
        const Json::Value& val = _root[key];
        if (val.isConvertibleTo(Json::ValueType::booleanValue))
            return val.asBool();
        throw except("type mismatch for " + key);
    }

    template<>
    std::string json::get<std::string>(const std::string& key) const
    {
        check(key);
        const Json::Value& val = _root[key];
        if (val.isConvertibleTo(Json::ValueType::stringValue))
            return val.asString();
        throw except("type mismatch for " + key);
    }

    template<>
    time_t json::get<time_t>(const std::string& key) const
    {
        check(key);
        const Json::Value& val = _root[key];
        if (val.isConvertibleTo(Json::ValueType::uintValue))
            return val.asInt();
        throw except("type mismatch for " + key);
    }

    template<>
    int json::get<int>(const std::string& key) const
    {
        check(key);
        const Json::Value& val = _root[key];
        if (val.isConvertibleTo(Json::ValueType::intValue))
            return val.asUInt();
        throw except("type mismatch for " + key);
    }

    template<>
    size_t json::get<size_t>(const std::string& key) const
    {
        check(key);
        const Json::Value& val = _root[key];
        if (val.isConvertibleTo(Json::ValueType::uintValue))
            return val.asUInt();
        throw except("type mismatch for " + key);
    }

    template<>
    std::vector<std::string> json::get<std::vector<std::string>>(const std::string& key) const
    {
        check(key);
        const Json::Value& val = _root[key];
        std::vector<std::string> res;
        if (val.isConvertibleTo(Json::ValueType::arrayValue))
        {
            for (int i = 0; i < val.size(); i++)
                res.push_back(val[i].asString());
        }
        return res;
    }


struct time
{
    static time_t now()
    {
        return std::time(nullptr);
    }
};

namespace mysql
{
struct connection : easymysql::connection
{
    connection(const std::string& host,
               const std::string& user,
               const std::string& passwd,
               const std::string& db,
               uint16_t port = 3306)
        : easymysql::connection(host, user, passwd, db, port)
    {}
};

struct executer : easymysql::executer
{
    executer(easymysql::connection& conn)
        : easymysql::executer(conn)
    {}
};

struct result : easymysql::result
{
    result(easymysql::connection& conn)
        : easymysql::result(conn)
    {}
};
}

#if defined (__APPLE__)
// macOS platform does not support anonymous semaphores.
class semaphore
{
public:
    semaphore(int val) : _name("/semaphore_cloud_util")
    {
        if (sem_open(_name, O_RDONLY) != SEM_FAILED)
            sem_unlink(_name);

        _sem = sem_open(_name, O_CREAT | O_EXCL, 0644, val);

        if (_sem == SEM_FAILED)
            throw std::runtime_error("semaphore error");
    }
    ~semaphore()
    {
        sem_close(_sem);
        sem_unlink(_name);
    }
    void wait() { sem_wait(_sem); }
    void post() { sem_post(_sem); }

private:
    sem_t* _sem;
    const char* _name;
};

#elif defined (__linux__)
class semaphore
{
public:
    semaphore(int val) { sem_init(&_sem, 0, val); }
    ~semaphore() { sem_destroy(&_sem); }
    void wait() { sem_wait(&_sem); }
    void post() { sem_post(&_sem); }

    size_t value()
    {
        int val;
        sem_getvalue(&_sem, &val);
        return val;
    }

private:
    sem_t _sem;
};
#elif defined (_WIN32) || defined (_WIN64)
class semaphore
{
public:
    semaphore(int val)
    {
        if (val == 0)
        {
			_sem = CreateSemaphore(nullptr, 1, LONG_MAX, LPCTSTR());
			wait();
        }
        else
        {
			_sem = CreateSemaphore(nullptr, val, LONG_MAX, LPCTSTR());
        }
    }
    ~semaphore() { CloseHandle(_sem);                     }
    void wait()  { WaitForSingleObject(_sem, sec * 1000); }
    void post()  { ReleaseSemaphore(_sem, 1, nullptr);    }

    size_t value()
    {
        LPLONG i = std::unique_ptr<long>(new long(0)).get();
        ReleaseSemaphore(_sem, 1, i);
        wait();
        return *i;
    }

private:
    HANDLE _sem;
};
#endif

struct killer
{
#if defined (__APPLE__) || defined (__linux__)
    static void kill_self()
    {
        raise(SIGKILL);
    }
#elif defined (_WIN32) || defined (_WIN64)
    static void kill_self()
    {
        HANDLE handle = GetCurrentProcess();
        TerminateProcess(handle, 0);
        CloseHandle(handle);
    }
#endif
};


} // namespace util
} // namespace cloud
